package fakecmdcommon

import (
	"fmt"
	"reflect"
	"strings"

	"go.uber.org/zap"
)

func init() {
	handlerMap = make(map[string]*InternalCmdInvoker)
}

var handlerMap map[string]*InternalCmdInvoker

func Register(cmdHandler interface{}) {
	rType := reflect.TypeOf(cmdHandler)
	numField := rType.Elem().NumField()

	for i := 0; i < numField; i++ {
		field := rType.Elem().Field(i)
		tagDescriptor := field.Tag.Get("InternalCmd")
		if tagDescriptor == "" {
			continue
		}
		defer func() {
			if err := recover(); err != nil {
				//TODO err的类型怎么限定
				zap.L().Error("协助处理器注册失败", zap.String("className", rType.String()), zap.Any("error", err))
			}
		}()
		cmdTag := parseProtoTag(tagDescriptor)
		method, exist := rType.MethodByName(cmdTag.method)
		if !exist {
			panic(fmt.Sprintf("%T中未找到可导出（首字母大写）的方法%v\n", cmdHandler, cmdTag.method))
		}

		//方法的参数分别是：0是Handler的指针，1是参数列表第一个
		prType := method.Type.In(1).Elem()

		cmd := prType.Name()
		_, duplicate := handlerMap[cmd]
		if duplicate {
			panic(fmt.Sprintf("内部消息%s处理器重复注册", cmd))
		}

		handler := &InternalCmdInvoker{
			cmd:     cmd,
			invoker: cmdHandler,
			method:  method,
		}
		handlerMap[handler.cmd] = handler
		fmt.Println(handler.cmd, handler)
	}
}

type InternalCmdInvoker struct {
	cmd     string
	invoker interface{}
	method  reflect.Method
}

type IInternalCmdMessage interface {
	OnException(err error)
}

type cmdTag struct {
	method string
}

func parseProtoTag(tagDescriptor string) (cmdTag cmdTag) {
	keyValues := strings.SplitN(tagDescriptor, ",", -1)
	for _, v := range keyValues {
		keyValue := strings.SplitN(v, "=", -1)
		switch keyValue[0] {
		case "method":
			cmdTag.method = keyValue[1]
		default:
		}
	}
	return
}

func Invoke(message IInternalCmdMessage) {
	rType := reflect.TypeOf(message).Elem()
	invoker, ok := handlerMap[rType.Name()]
	if !ok {
		zap.L().Info("未找到内部消息的处理器", zap.String("cmd", rType.Name()))
		return
	}
	callArguments := make([]reflect.Value, 2)
	callArguments[0] = reflect.ValueOf(invoker.invoker)
	callArguments[1] = reflect.ValueOf(message)
	defer func() {
		if err := recover(); err != nil {
			//TODO err的类型怎么限定
			message.OnException(fmt.Errorf("处理失败%v", err))
			zap.L().Error("处理失败", zap.String("cmd", rType.Name()), zap.Any("error", err))
		}
	}()
	invoker.method.Func.Call(callArguments)
}
